; Copyright (C) 2021 Rain

; This file is part of XNIX. 
; XNIX is free software: you can redistribute it and/or modify 
; it under the terms of the GNU General Public License as published by 
; the Free Software Foundation, either version 3 of the License, or 
; (at your option) and later version. 

; XNIX is distributed in the hope that it will be useful, 
; but WITHOUT ANY WARRANTY; without even the implied warranty of 
; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 
; GNU General Public License for more details. 

; You should have received a copy of the GNU General Public License 
; along with XNIX. If not, see <https://www.gnu.org/licenses/>.  




org 0x7e60


%include "boot/paging.asm"


; yeah, it's really 0x7c00 because I don't want to use other memory. 

BOOTDRIVER 	equ 0x7c00
VMODE	 	equ 0x7c01
SCREENX 	equ 0x7c02
SCREENY 	equ 0x7c04
FB_ADDR 	equ 0x7c06
CURSORX		equ 0x7c0a
CURSORY 	equ 0x7c0b



_start:
	xor ax, ax

	mov ds, ax
	mov es, ax

	mov ss, ax
	mov sp, 0x7c00

	; put a message on screen
	mov di, msg
	call print

	call load_ksetup

.set_vga:
	; well, now we use the VGA text mode, so...

	mov byte [VMODE], 0x00
	mov word [SCREENX], 80
	mov word [SCREENY], 25
	mov dword [FB_ADDR], 0xb8000

	; we can't put any messages after this, because kernel will 
	; use this to put messages. 

	; if we put msgs again, so must get cursor position again. 

.get_cursor:
	mov ah, 3

	; page number
	xor bh, bh

	int 0x10

	; dh: y  dl: x
	mov [CURSORX], dl
	mov [CURSORY], dh

.open_a20:
	; disable interrupts before we initial done (enable in kernel)

	; A20 line uses to control memory higher than 1MB. we can't access mem > 1MB when it's closing. 
	; So we must open it before switching to protect-mode. 

	cli

	in al, 0x92
	or al, 0x02
	out 0x92, al

.to_prot:
	; switch to protect-mode. 
	
	lgdt [gdt32_desc]
	
	; CR0 (control register 0) uses to control CPU. It's first bit PE controls Protect-mode Enable. 
	mov eax, cr0
	or eax, 1
	mov cr0, eax

	; CPU is in protect-mode now, but segment registers didn't initialze. 
	
	; we cannot 'mov cs, xxx'

	jmp dword code32:init_prot

print:
	pushaw
	; message in register di, but lodsb uses si. 
	mov si, di

.loop:
	cld
	lodsb
	
	; zero? -> return
	test al, al
	jz .ret
	
	mov ah, 0x0e
	mov bx, 0x000f
	
	int 0x10

	jmp .loop

.ret:
	popaw
	ret


MAGIC		equ 	0
NINODES		equ	4
NBLOCKS 	equ 	8

NB_BBM 		equ 	12
NB_IBM 		equ 	16

BBM_BN 		equ 	20
IBM_BN 		equ 	24

NB_BIM 		equ 	28
NB_INODES 	equ 	32

BIM_BN 		equ 	36
INODES_BN 	equ 	40

ROOT_INO 	equ 	44
INODE_SIZE 	equ 	32

FIRSTDATABLOCK	equ 	76
NEXT_INODE	equ 	80

ZERO 		equ 	84

DENTRY_NAME 	equ 	4


load_ksetup:
	; find 'ksetup' in the root-directory, and then read it to 0x8000
	; get the inode of 'ksetup', then we can find in bim. 

	; the first block of root-inode must be first-data-block
	mov eax, [0x7e00 + FIRSTDATABLOCK]

	; the fs buffer
	mov bx, 0x5000

	call read_block

	; and start find in this directory
	mov si, 0x5000 + DENTRY_NAME
	mov di, fname
	mov cx, 6

.cmp_loop:
	repe
	cmpsb

	jz found

	; not found, continue
	add si, 128
	cmp si, 0x6000
	
	jae load_failed
	jmp .cmp_loop

found:
	mov di, found_msg
	call print

.get_inode:
	; store its inode number in te eax
	; 6: the length of file name

	mov ecx, [si - 6 - DENTRY_NAME]

	; and then read this inode (only read the first block of inodes)
	mov eax, [0x7e00 + INODES_BN]
	mov bx, 0x5000

	call read_block

	; back up the inode nr, for .search_bim
	mov edx, ecx
	
	; * sizeof(struct inode)
	shl cx, 5

	mov si, 0x5000
	add si, cx

	; and get its size

INO_INODE 	equ 	0
INO_PINODE 	equ 	4
INO_ACCESS 	equ 	8
INO_BLOCKSNR 	equ 	9
INO_UPSIZE 	equ 	13
INO_CTIME 	equ 	17
INO_WTIME 	equ 	21
INO_NLINKS 	equ 	25
INO_UID 	equ 	26
INO_GID 	equ 	28
INO_EACCESS 	equ 	30

	mov eax, [si + INO_BLOCKSNR]

	cmp dword [si + INO_UPSIZE], 0

	; upsize is zero, so eax is blocks we need. 
	je .read_ksetup

	; okay, this is enough
	inc eax

.read_ksetup:
	; find in the bim (only first block, too)
	; first read the first block of bim to 0x5000

	; blocks need is in the eax, save it 

BIM_INODE 	equ 	0
BIM_THBLOCK	equ 	4

	mov ecx, eax

	mov eax, [0x7e00 + BIM_BN]
	mov bx, 0x5000

	call read_block

	; and start search
	mov si, 0x5000

	mov eax, ecx
	xor ecx, ecx
	; the to address

	; and this is the address of kernel
	xor bx, bx

	push eax

	mov ax, 0x0900
	mov es, ax

	pop eax

.search_bim:
	cmp ecx, eax

	; ecx is the blocks we loaded (also bim->th_block), eax is the blocks of this file
	je .load_done

	; the inode of this file is edx
	cmp edx, [si]

	; not match, search again
	jne .search_again

	cmp ecx, [si + BIM_THBLOCK]
	jne .search_again


.load_file:
	; only one block, and coutinue search
	; the block is the index of si (base is 0x5000), [si] is the inode

	; not this!!! loader must uses 32-bit registers, so we should save/restore 
	; them in 32-bits
	; pushaw

	push eax
	push ecx
	push edx
	push ebx

	push esp
	push ebp
	push esi
	push edi

	; first get the block nr
	xor eax, eax
	mov ax, si

	; - base
	sub eax, 0x5000

	; / 8, get the index
	shr eax, 3

	; and += first_data_block, because bim start at this. 
	add eax, [0x7e00 + FIRSTDATABLOCK]

	; now eax is the real block nr of this file

	; read it! 
	; bx is the address, we don't need change
	call read_block

	pop edi
	pop esi
	pop ebp
	pop esp

	pop ebx
	pop edx
	pop ecx
	pop eax

	add bx, 4096
	inc ecx

	add si, 8

	jmp .search_bim

.search_again:
	; the size of a bim entry
	add si, 8
	cmp si, 0x6000

	jae load_failed
	jmp .search_bim

.load_done:
	mov di, load_done_msg
	call print

	ret

load_failed:
	mov di, load_failed_msg
	call print

die:	hlt
	jmp die

read_block:
	push ax
	push cx

	; 8 sectors = 4 KB = 1 block 
	mov cx, 8
	shl eax, 3

.loop:
	call read_lba

	inc eax
	dec cx

	add bx, 512
	cmp bx, 0

	je .go_on_es

.cont:
	cmp cx, 0
	jne .loop

	pop cx
	pop ax

	ret

.go_on_es:
	; update the es, and continue
	push ax

	mov ax, es
	add ax, 512 / 16
	mov es, ax

	pop ax

	jmp .cont


read_lba:
	push eax
	push ecx
	push edx
	push ebx

	push esp
	push ebp
	push esi
	push edi

	push dword 0x0000
	push dword eax

	; buffer address
	push word es
	push word bx

	; sectors count
	push word cx

	; package size
	push word 16

	mov ah, 0x42
	mov dl, [BOOTDRIVER]
	mov si, sp

	int 0x13

	add sp, 16
	
	pop edi
	pop esi
	pop ebp
	pop esp

	pop ebx
	pop edx
	pop ecx
	pop eax

	jc read_failed

	ret

read_failed:
	mov di, read_failed_msg
	call print

	jmp die


bits 32

init_prot:
	; we set segment selectors for segment register (CS was set by far-jmp)
	mov ax, data32

	mov ds, ax
	mov es, ax
	mov fs, ax
	mov gs, ax

	mov ss, ax

	mov sp, 0x00007c00

	; now we switch to long-mode

.to_long_mode:
	; clear page map 0 - 0x4000 (0x3000-0x4000 maybe not used)
	xor edi, edi
	; mov edi, 0x00000000

	xor eax, eax
	; store for 4096 times
	mov ecx, 4096

	cld
	rep stosd

.init_paging:
	; we set page map at 0x00000000 (a good address)

	mov dword [0x00000000], 0x00001000 | PAGE_DIR
;	mov dword [0x00000004], 0x00000000

	mov dword [0x00000800], 0x00001000 | PAGE_DIR
;	mov dword [0x00000804], 0x00000000

	; and page directory pointer table

	mov dword [0x00001000], 0x00002000 | PAGE_DIR
;	mov dword [0x00001004], 0x00000000

	; page directory entry (2 MB)

	mov dword [0x00002000], 0x00000000 | PAGE_KERNEL
;	mov dword [0x00002004], 0x00000000

	mov dword [0x00002008], 0x00200000 | PAGE_KERNEL
;	mov dword [0x0000200c], 0x00000000


.enable_long_mode:
	lgdt [gdt64_desc]

	mov ax, data64

	mov ds, ax
	mov es, ax
	mov fs, ax
	mov gs, ax
	mov ss, ax

	; open PAE (page map level 3)
	mov eax, cr4
	bts eax, 5
	mov cr4, eax

	; set Page Table Base Register

	; mov eax, 0x00000000
	xor eax, eax
	mov cr3, eax

	; enable long mode (must before paging)

	mov ecx, 0xc0000080
	rdmsr

	bts eax, 8
	wrmsr

	; enable paging

	mov eax, cr0

	; PE again
	or eax, 1
	bts eax, 31
	
	mov cr0, eax

	; okay! now set segment registers and init long mode
	
	jmp code64:0x9000

msg: db "welcome to XNIX Loader. ", 0x0d, 0x0a, 0x00
read_failed_msg: db "fatal: read disk failed. ", 0x0d, 0x0a, 0x00
fname: db "ksetup", 0x00

found_msg: db "found ksetup in the root. ", 0x0d, 0x0a, 0x00
load_failed_msg: db "failed when trying to load ksetup. ", 0x0d, 0x0a, 0x00
load_done_msg: db "load ksetup done. ", 0x0d, 0x0a, 0x00

alignb 16

gdt32:
	dd 0x00000000, 0x00000000

gdt32_code:
	dd 0x0000ffff, 0x00cf9a00

gdt32_data:
	dd 0x0000ffff, 0x00cf9200

gdt32_len equ $ - gdt32

gdt32_desc:
	dw gdt32_len - 1
	dd gdt32

code32 equ gdt32_code - gdt32
data32 equ gdt32_data - gdt32



gdt64:
	dq 0x0000000000000000

gdt64_code:
	dq 0x0020980000000000

gdt64_data:
	dq 0x0000920000000000

gdt64_len equ $ - gdt64

gdt64_desc:
	dw gdt64_len - 1
	dd gdt64

code64 equ gdt64_code - gdt64
data64 equ gdt64_data - gdt64

